package redis.clients.jedis.tests;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.jedis.tests.utils.JedisSentinelTestUtil;

public class JedisSentinelTest extends JedisTestBase {
    private static final String MASTER_NAME = "mymaster";
    private static final String MONITOR_MASTER_NAME = "mymastermonitor";
    private static final String REMOVE_MASTER_NAME = "mymasterremove";
    private static final String FAILOVER_MASTER_NAME = "mymasterfailover";
    private static final String MASTER_IP = "127.0.0.1";

    protected static HostAndPort master = HostAndPortUtil.getRedisServers()
	    .get(0);
    protected static HostAndPort slave = HostAndPortUtil.getRedisServers()
	    .get(4);
    protected static HostAndPort sentinel = HostAndPortUtil
	    .getSentinelServers().get(0);

    protected static HostAndPort sentinelForFailover = HostAndPortUtil
	    .getSentinelServers().get(2);
    protected static HostAndPort masterForFailover = HostAndPortUtil
	    .getRedisServers().get(5);

    @Before
    public void setup() throws InterruptedException {
    }

    @After
    public void clear() throws InterruptedException {
	// New Sentinel (after 2.8.1)
	// when slave promoted to master (slave of no one), New Sentinel force
	// to restore it (demote)
	// so, promote(slaveof) slave to master has no effect, not same to old
	// Sentinel's behavior
	ensureRemoved(MONITOR_MASTER_NAME);
	ensureRemoved(REMOVE_MASTER_NAME);
    }

    @Test
    public void sentinel() {
	Jedis j = new Jedis(sentinel.getHost(), sentinel.getPort());

	try {
	    List<Map<String, String>> masters = j.sentinelMasters();

	    boolean inMasters = false;
	    for (Map<String, String> master : masters)
		if (MASTER_NAME.equals(master.get("name")))
		    inMasters = true;

	    assertTrue(inMasters);

	    List<String> masterHostAndPort = j
		    .sentinelGetMasterAddrByName(MASTER_NAME);
	    HostAndPort masterFromSentinel = new HostAndPort(
		    masterHostAndPort.get(0),
		    Integer.parseInt(masterHostAndPort.get(1)));
	    assertEquals(master, masterFromSentinel);

	    List<Map<String, String>> slaves = j.sentinelSlaves(MASTER_NAME);
	    assertTrue(slaves.size() > 0);
	    assertEquals(master.getPort(),
		    Integer.parseInt(slaves.get(0).get("master-port")));

	    // DO NOT RE-RUN TEST TOO FAST, RESET TAKES SOME TIME TO... RESET
	    assertEquals(Long.valueOf(1), j.sentinelReset(MASTER_NAME));
	    assertEquals(Long.valueOf(0), j.sentinelReset("woof" + MASTER_NAME));
	} finally {
	    j.close();
	}
    }

    @Test
    public void sentinelFailover() throws InterruptedException {
	Jedis j = new Jedis(sentinelForFailover.getHost(),
		sentinelForFailover.getPort());

	try {
	    List<String> masterHostAndPort = j
		    .sentinelGetMasterAddrByName(FAILOVER_MASTER_NAME);
	    HostAndPort currentMaster = new HostAndPort(masterHostAndPort.get(0), 
		    Integer.parseInt(masterHostAndPort.get(1)));
	    String result = j.sentinelFailover(FAILOVER_MASTER_NAME);
	    assertEquals("OK", result);

	    JedisSentinelTestUtil.waitForNewPromotedMaster(j);

	    masterHostAndPort = j
		    .sentinelGetMasterAddrByName(FAILOVER_MASTER_NAME);
	    HostAndPort newMaster = new HostAndPort(masterHostAndPort.get(0),
		    Integer.parseInt(masterHostAndPort.get(1)));

	    assertNotEquals(newMaster, currentMaster);
	} finally {
	    j.close();
	}

    }

    @Test
    public void sentinelMonitor() {
	Jedis j = new Jedis(sentinel.getHost(), sentinel.getPort());

	try {
	    // monitor new master
	    String result = j.sentinelMonitor(MONITOR_MASTER_NAME, MASTER_IP,
		    master.getPort(), 1);
	    assertEquals("OK", result);

	    // already monitored
	    try {
		j.sentinelMonitor(MONITOR_MASTER_NAME, MASTER_IP,
			master.getPort(), 1);
		fail();
	    } catch (JedisDataException e) {
		// pass
	    }
	} finally {
	    j.close();
	}
    }

    @Test
    public void sentinelRemove() {
	Jedis j = new Jedis(sentinel.getHost(), sentinel.getPort());

	try {
	    ensureMonitored(sentinel, REMOVE_MASTER_NAME, MASTER_IP,
		    master.getPort(), 1);

	    String result = j.sentinelRemove(REMOVE_MASTER_NAME);
	    assertEquals("OK", result);

	    // not exist
	    try {
		result = j.sentinelRemove(REMOVE_MASTER_NAME);
		assertNotEquals("OK", result);
		fail();
	    } catch (JedisDataException e) {
		// pass
	    }
	} finally {
	    j.close();
	}
    }

    @Test
    public void sentinelSet() {
	Jedis j = new Jedis(sentinel.getHost(), sentinel.getPort());

	try {
	    Map<String, String> parameterMap = new HashMap<String, String>();
	    parameterMap.put("down-after-milliseconds", String.valueOf(1234));
	    parameterMap.put("parallel-syncs", String.valueOf(3));
	    parameterMap.put("quorum", String.valueOf(2));
	    j.sentinelSet(MASTER_NAME, parameterMap);

	    List<Map<String, String>> masters = j.sentinelMasters();
	    for (Map<String, String> master : masters) {
		if (master.get("name").equals(MASTER_NAME)) {
		    assertEquals(1234, Integer.parseInt(master
			    .get("down-after-milliseconds")));
		    assertEquals(3,
			    Integer.parseInt(master.get("parallel-syncs")));
		    assertEquals(2, Integer.parseInt(master.get("quorum")));
		}
	    }

	    parameterMap.put("quorum", String.valueOf(1));
	    j.sentinelSet(MASTER_NAME, parameterMap);
	} finally {
	    j.close();
	}
    }

    private void ensureMonitored(HostAndPort sentinel, String masterName,
	    String ip, int port, int quorum) {
	Jedis j = new Jedis(sentinel.getHost(), sentinel.getPort());
	try {
	    j.sentinelMonitor(masterName, ip, port, quorum);
	} catch (JedisDataException e) {
	} finally {
	    j.close();
	}
    }

    private void ensureRemoved(String masterName) {
	Jedis j = new Jedis(sentinel.getHost(), sentinel.getPort());
	try {
	    j.sentinelRemove(masterName);
	} catch (JedisDataException e) {
	} finally {
	    j.close();
	}
    }
    
}
